#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 
#include <doctest/doctest.h>

#include <array>

#include <oess_2/io/h/bstring_buf.hpp>
#include <oess_2/io/h/fixed_mem_buf.hpp>

#include <arataga/dns_resolver/dns_types.hpp>

using namespace arataga::dns_resolver;
using namespace std;
using namespace std::string_view_literals;

TEST_CASE( "DNS Header flags" )
{
	dns_header_t header(42);

	REQUIRE( header.qr() == dns_header_t::REQUEST );

	header.set_qr( dns_header_t::RESPONSE );

	REQUIRE( header.qr() == dns_header_t::RESPONSE );

	REQUIRE( header.rd() == true );

	header.set_rd( false );

	REQUIRE( header.rd() == false );
}

TEST_CASE( "DNS-format name" )
{
	{
		dns_format_name_t empty;

		REQUIRE( 1u == empty.raw_value().size() );
		REQUIRE( '\0' == empty.raw_value().at(0u) );

		std::ostringstream ss;
		ss << empty;
		REQUIRE( "" == ss.str() );
	}

	{
		dns_format_name_t empty{ "." };

		REQUIRE( 1u == empty.raw_value().size() );
		REQUIRE( '\0' == empty.raw_value().at(0u) );

		std::ostringstream ss;
		ss << empty;
		REQUIRE( "" == ss.str() );
	}

	{
		dns_format_name_t check{ "www" };

		REQUIRE( 5u == check.raw_value().size() );
		REQUIRE( '\3' == check.raw_value().at(0u) );
		REQUIRE( '\0' == check.raw_value().at(4u) );

		std::ostringstream ss;
		ss << check;
		REQUIRE( "www." == ss.str() );
	}

	{
		dns_format_name_t etalon( "www.yandex.ru." );

		std::string buf;
		oess_2::io::obstring_t obuf( buf );
		REQUIRE_NOTHROW( obuf << etalon );
		oess_2::io::ibstring_t ibuf( buf );
		dns_format_name_t check;
		REQUIRE_NOTHROW( ibuf >> from_stream( check ) );

		REQUIRE( etalon == check );
	}
	{
		static constexpr char actual_data[] = {
			0x00, 0x2a, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
			0x00, 0x00, 0x00, 0x00,
			0x03, 'w', 'w', 'w',
			0x06, 'g', 'o', 'o', 'g', 'l', 'e',
			0x03, 'c', 'o', 'm',
			0x00,
			0x04, 'm', 'a', 'i', 'l',
			static_cast<char>(0xc0), 0x10, // Reference to google.com.
			0x00
		};
		std::string_view all_actual_data(
				actual_data, sizeof actual_data );

		dns_header_t header;
		dns_format_name_t name1;
		dns_format_name_t name2;

		oess_2::io::ifixed_mem_buf_t ibuf(
				all_actual_data.data(),
				all_actual_data.size() );

		ibuf >> header >> from_memory( all_actual_data, name1 )
				>> from_memory( all_actual_data, name2 );

		REQUIRE( name1 == dns_format_name_t{ "www.google.com." } );
		REQUIRE( name2 == dns_format_name_t{ "mail.google.com." } );
	}
}

TEST_CASE( "Invalid values for DNS-format name [invalid_dns_format_name]" )
{
	{
		dns_format_name_t check;

		const std::string_view illegal_values[] = {
				".."sv,
				"www.."sv,
				".ru"sv,
				"www..yandex.ru"sv
		};
		for( const auto sv : illegal_values )
		{
			std::cout << "checking: '" << sv << "'" << std::flush;
			REQUIRE_THROWS_AS( check = dns_format_name_t{ sv },
					std::invalid_argument );
			std::cout << std::endl;
		}
	}

	{
		// An attempt to set name with a label longer that 63 symbols should fail.
		REQUIRE_THROWS_AS(
				dns_format_name_t(
						"123456789."
						"123456789_123456789_123456789_123456789_"
								"123456789_123456789_1234."
						"123456789." "1234." ),
				std::invalid_argument );
	}

	{
		// An attempt to set name longer that 254 symbols should fail.
		REQUIRE_THROWS_AS(
				dns_format_name_t(
						"123456789." "123456789." "123456789." "123456789."
						"123456789." "123456789." "123456789." "123456789."
						"123456789." "123456789." "123456789." "123456789."
						"123456789." "123456789." "123456789." "123456789."
						"123456789." "123456789." "123456789." "123456789."
						"123456789." "123456789." "123456789." "123456789."
						"123456789." "1234." ),
				std::invalid_argument );
	}

	{
		// An attempt to load name longer than 254 symbols should fail.
		const std::string_view all_data{
				"\x09""123456789""\x09""123456789""\x09""123456789""\x09""123456789"
				"\x09""123456789""\x09""123456789""\x09""123456789""\x09""123456789"
				"\x09""123456789""\x09""123456789""\x09""123456789""\x09""123456789"
				"\x09""123456789""\x09""123456789""\x09""123456789""\x09""123456789"
				"\x09""123456789""\x09""123456789""\x09""123456789""\x09""123456789"
				"\x09""123456789""\x09""123456789""\x09""123456789""\x09""123456789"
				"\x09""123456789""\x04""1234\x00"
		};

		oess_2::io::ifixed_mem_buf_t stream( all_data.data(), all_data.size() );

		dns_format_name_t name;
		REQUIRE_THROWS_AS(
				stream >> from_stream( name ),
				std::invalid_argument );
	}

	{
		// Attempt to collect name longer than 254 symbols should fail.
		static constexpr char data[] = {
				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00, // End of header.
				0x03, 'w', 'w', 'w',
				static_cast<char>(0xC0), static_cast<char>(0x0C) // Make the loop.
		};

		const std::string_view all_data( data, sizeof(data) );
		oess_2::io::ifixed_mem_buf_t stream( all_data.data(), all_data.size() );

		dns_header_t header;
		dns_format_name_t name;

		stream >> header;
		REQUIRE_THROWS_AS(
				stream >> from_memory( all_data, name ),
				std::invalid_argument );
	}

	{
		// An attempt to load name with infinite loop should fail.
		static constexpr char data[] = {
				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00, // End of header.
				static_cast<char>(0xC0), static_cast<char>(0x0C) // Make the loop.
		};

		const std::string_view all_data( data, sizeof(data) );
		oess_2::io::ifixed_mem_buf_t stream( all_data.data(), all_data.size() );

		dns_header_t header;
		dns_format_name_t name;

		stream >> header;
		REQUIRE_THROWS_AS(
				stream >> from_memory( all_data, name ),
				std::runtime_error );
	}
}

TEST_CASE( "DNS Question" )
{
	dns_question_t question( "www.bash.im" );

	std::cout << question << std::endl;
}

#if 0
TEST_CASE( "DNS Resourse Record" )
{
	dns_resource_record_t etalon;
	etalon.m_name = dns_format_name_t( "www.google.ru." );
	etalon.m_type = dns_type::A;
	etalon.m_class = 1;
	etalon.m_ttl = 100;
	etalon.m_resource_data = "8.8.8.8";

	std::string buf;
	oess_2::io::obstring_t obuf( buf );
	obuf << etalon;
	oess_2::io::ibstring_t ibuf( buf );
	dns_resource_record_t check;
	ibuf >> from_memory( buf, check );


	REQUIRE( etalon.m_name.value() == check.m_name.value() );
	REQUIRE( etalon.m_type == check.m_type );
	REQUIRE( etalon.m_class == check.m_class );
	REQUIRE( etalon.m_ttl == check.m_ttl );
}
#endif

void
write_to_buf( oess_2::io::ostream_t & o );

TEST_CASE( "Read DNS Response" )
{
	std::array<char, 65536> buf = {{ 0 }};
	std::string_view all_buf( buf.data(), buf.size() );
	oess_2::io::ofixed_mem_buf_t obuf( buf.data(), buf.size() );

	write_to_buf( obuf );

	oess_2::io::ifixed_mem_buf_t ibuf{ buf.data(), buf.size() };

	dns_header_t header;
	ibuf >> header;

	// std::cout << header << std::endl;

	// std::cout <<  std::endl;

	for( int i = 0 ; i < header.m_qdcount ; ++i )
	{
		dns_question_t question;
		ibuf >> question;

		// std::cout << question << std::endl;
	}

	std::cout <<  std::endl;

	for( int i = 0 ; i < header.m_ancount ; ++i )
	{
		dns_resource_record_t rr;
		ibuf >> from_memory( all_buf, rr );

		// std::cout << rr << std::endl;
	}

	std::cout <<  std::endl;

	for( int i = 0 ; i < header.m_nscount ; ++i )
	{
		dns_resource_record_t rr;
		ibuf >> from_memory( all_buf, rr );

		// std::cout << rr << std::endl;
	}

	std::cout <<  std::endl;

	for( int i = 0 ; i < header.m_arcount ; ++i )
	{
		dns_resource_record_t rr;
		ibuf >> from_memory( all_buf, rr );

		// std::cout << rr << std::endl;
	}
}

void
test_response_dns4_name_services_com( oess_2::io::ostream_t & o );

TEST_CASE( "Read DNS Big Response" )
{
	std::array<char, 65536> buf = {{ 0 }};
	std::string_view all_buf( buf.data(), buf.size() );
	oess_2::io::ofixed_mem_buf_t obuf( buf.data(), buf.size() );

	test_response_dns4_name_services_com( obuf );

	oess_2::io::ifixed_mem_buf_t ibuf{ buf.data(), buf.size() };

	dns_header_t header;
	ibuf >> header;

	for( int i = 0 ; i < header.m_qdcount ; ++i )
	{
		dns_question_t question;
		ibuf >> question;
	}

	for( int i = 0 ; i < header.m_ancount ; ++i )
	{
		dns_resource_record_t rr;
		ibuf >> from_memory( all_buf, rr );
	}

	for( int i = 0 ; i < header.m_nscount ; ++i )
	{
		dns_resource_record_t rr;
		ibuf >> from_memory( all_buf, rr );
	}

	for( int i = 0 ; i < header.m_arcount ; ++i )
	{
		dns_resource_record_t rr;
		ibuf >> from_memory( all_buf, rr );
	}
}

void
write_to_buf( oess_2::io::ostream_t & o )
{
	static constexpr oess_2::uchar_t data[] = {
		0x00, 0x01, 0x81, 0x80,

		0x00, 0x01, 0x00, 0x04, 0x00, 0x02, 0x00, 0x05,
		0x03, 0x77, 0x77, 0x77, 0x06, 0x79, 0x61, 0x6e,

		0x64, 0x65, 0x78, 0x02, 0x72, 0x75, 0x00, 0x00,
		0x01, 0x00, 0x01, 0xc0, 0x0c, 0x00, 0x01, 0x00,

		0x01, 0x00, 0x00, 0x00, 0xb8, 0x00, 0x04, 0x4d,
		0x58, 0x37, 0x37, 0xc0, 0x0c, 0x00, 0x01, 0x00,

		0x01, 0x00, 0x00, 0x00, 0xb8, 0x00, 0x04, 0x4d,
		0x58, 0x37, 0x42, 0xc0, 0x0c, 0x00, 0x01, 0x00,

		0x01, 0x00, 0x00, 0x00, 0xb8, 0x00, 0x04, 0x05,
		0xff, 0xff, 0x05, 0xc0, 0x0c, 0x00, 0x01, 0x00,

		0x01, 0x00, 0x00, 0x00, 0xb8, 0x00, 0x04, 0x05,
		0xff, 0xff, 0x37, 0xc0, 0x10, 0x00, 0x02, 0x00,

		0x01, 0x00, 0x00, 0xd9, 0x50, 0x00, 0x06, 0x03,
		0x6e, 0x73, 0x32, 0xc0, 0x10, 0xc0, 0x10, 0x00,

		0x02, 0x00, 0x01, 0x00, 0x00, 0xd9, 0x50, 0x00,
		0x06, 0x03, 0x6e, 0x73, 0x31, 0xc0, 0x10, 0xc0,

		0x7d, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0xd9,
		0x50, 0x00, 0x04, 0xd5, 0xb4, 0xc1, 0x01, 0xc0,

		0x7d, 0x00, 0x1c, 0x00, 0x01, 0x00, 0x00, 0xd9,
		0x50, 0x00, 0x10, 0x2a, 0x02, 0x06, 0xb8, 0x00,

		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x01, 0xc0, 0x6b, 0x00, 0x01, 0x00,

		0x01, 0x00, 0x00, 0xd9, 0x50, 0x00, 0x04, 0x5d,
		0x9e, 0x86, 0x01, 0xc0, 0x6b, 0x00, 0x1c, 0x00,

		0x01, 0x00, 0x00, 0xd9, 0x50, 0x00, 0x10, 0x2a,
		0x02, 0x06, 0xb8, 0x00, 0x00, 0x00, 0x01, 0x00,

		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
		0x00, 0x29, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00,

		0x00, 0x00
	};

	o.write( data, sizeof(data) );
}

void
test_response_dns4_name_services_com( oess_2::io::ostream_t & o )
{
	static constexpr oess_2::uchar_t data[] = {
		0x00, 0x01, 0x80, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0d,
		0x00, 0x0e, 0x04, 0x64, 0x6e, 0x73, 0x34, 0x0d, 0x6e, 0x61,
		0x6d, 0x65, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
		0x73, 0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01,
		0xc0, 0x1f, 0x00, 0x02, 0x00, 0x01, 0x00, 0x02, 0xa3, 0x00,
		0x00, 0x14, 0x01, 0x61, 0x0c, 0x67, 0x74, 0x6c, 0x64, 0x2d,
		0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x03, 0x6e, 0x65,
		0x74, 0x00, 0xc0, 0x1f, 0x00, 0x02, 0x00, 0x01, 0x00, 0x02,
		0xa3, 0x00, 0x00, 0x04, 0x01, 0x62, 0xc0, 0x36, 0xc0, 0x1f,
		0x00, 0x02, 0x00, 0x01, 0x00, 0x02, 0xa3, 0x00, 0x00, 0x04,
		0x01, 0x63, 0xc0, 0x36, 0xc0, 0x1f, 0x00, 0x02, 0x00, 0x01,
		0x00, 0x02, 0xa3, 0x00, 0x00, 0x04, 0x01, 0x64, 0xc0, 0x36,
		0xc0, 0x1f, 0x00, 0x02, 0x00, 0x01, 0x00, 0x02, 0xa3, 0x00,
		0x00, 0x04, 0x01, 0x65, 0xc0, 0x36, 0xc0, 0x1f, 0x00, 0x02,
		0x00, 0x01, 0x00, 0x02, 0xa3, 0x00, 0x00, 0x04, 0x01, 0x66,
		0xc0, 0x36, 0xc0, 0x1f, 0x00, 0x02, 0x00, 0x01, 0x00, 0x02,
		0xa3, 0x00, 0x00, 0x04, 0x01, 0x67, 0xc0, 0x36, 0xc0, 0x1f,
		0x00, 0x02, 0x00, 0x01, 0x00, 0x02, 0xa3, 0x00, 0x00, 0x04,
		0x01, 0x68, 0xc0, 0x36, 0xc0, 0x1f, 0x00, 0x02, 0x00, 0x01,
		0x00, 0x02, 0xa3, 0x00, 0x00, 0x04, 0x01, 0x69, 0xc0, 0x36,
		0xc0, 0x1f, 0x00, 0x02, 0x00, 0x01, 0x00, 0x02, 0xa3, 0x00,
		0x00, 0x04, 0x01, 0x6a, 0xc0, 0x36, 0xc0, 0x1f, 0x00, 0x02,
		0x00, 0x01, 0x00, 0x02, 0xa3, 0x00, 0x00, 0x04, 0x01, 0x6b,
		0xc0, 0x36, 0xc0, 0x1f, 0x00, 0x02, 0x00, 0x01, 0x00, 0x02,
		0xa3, 0x00, 0x00, 0x04, 0x01, 0x6c, 0xc0, 0x36, 0xc0, 0x1f,
		0x00, 0x02, 0x00, 0x01, 0x00, 0x02, 0xa3, 0x00, 0x00, 0x04,
		0x01, 0x6d, 0xc0, 0x36, 0xc0, 0x34, 0x00, 0x01, 0x00, 0x01,
		0x00, 0x02, 0xa3, 0x00, 0x00, 0x04, 0xc0, 0x05, 0x06, 0x1e,
		0xc0, 0x54, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0xa3, 0x00,
		0x00, 0x04, 0xc0, 0x21, 0x0e, 0x1e, 0xc0, 0x64, 0x00, 0x01,
		0x00, 0x01, 0x00, 0x02, 0xa3, 0x00, 0x00, 0x04, 0xc0, 0x1a,
		0x5c, 0x1e, 0xc0, 0x74, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02,
		0xa3, 0x00, 0x00, 0x04, 0xc0, 0x1f, 0x50, 0x1e, 0xc0, 0x84,
		0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0xa3, 0x00, 0x00, 0x04,
		0xc0, 0x0c, 0x5e, 0x1e, 0xc0, 0x94, 0x00, 0x01, 0x00, 0x01,
		0x00, 0x02, 0xa3, 0x00, 0x00, 0x04, 0xc0, 0x23, 0x33, 0x1e,
		0xc0, 0xa4, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0xa3, 0x00,
		0x00, 0x04, 0xc0, 0x2a, 0x5d, 0x1e, 0xc0, 0xb4, 0x00, 0x01,
		0x00, 0x01, 0x00, 0x02, 0xa3, 0x00, 0x00, 0x04, 0xc0, 0x36,
		0x70, 0x1e, 0xc0, 0xc4, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02,
		0xa3, 0x00, 0x00, 0x04, 0xc0, 0x2b, 0xac, 0x1e, 0xc0, 0xd4,
		0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0xa3, 0x00, 0x00, 0x04,
		0xc0, 0x30, 0x4f, 0x1e, 0xc0, 0xe4, 0x00, 0x01, 0x00, 0x01,
		0x00, 0x02, 0xa3, 0x00, 0x00, 0x04, 0xc0, 0x34, 0xb2, 0x1e,
		0xc0, 0xf4, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0xa3, 0x00,
		0x00, 0x04, 0xc0, 0x29, 0xa2, 0x1e, 0xc1, 0x04, 0x00, 0x01,
		0x00, 0x01, 0x00, 0x02, 0xa3, 0x00, 0x00, 0x04, 0xc0, 0x37,
		0x53, 0x1e, 0xc0, 0x34, 0x00, 0x1c, 0x00, 0x01, 0x00, 0x02,
		0xa3, 0x00, 0x00, 0x10, 0x20, 0x01, 0x05, 0x03, 0xa8, 0x3e,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x30
	};

	o.write( data, sizeof(data) );
}

